﻿using System;
using System.Diagnostics;
using System.Reflection;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace UnisimOperations
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private void StartBtnClicked(object sender, EventArgs e)
        {
            
            if (this.openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                UniSimDesign.SimulationCase sim_case = UnisimWrapper.Instance.NewSimCase(this.openFileDialog1.FileName);
                //UniSimDesign.SimulationCase sim_case = UnisimWrapper.Instance.FirstSimCase;
                sim_case.Activate();
            }
        }

        private void StartIntegratorBtn_Click(object sender, EventArgs e)
        {
            UniSimDesign.SimulationCase sim_case = UnisimWrapper.Instance.FirstSimCase;
            sim_case.Solver.Integrator.Continue();
            if (UnisimWrapper.Instance.properties.Count != 0)                            // Тупая проверка инициализации
                this.UpdateTimer.Enabled = true;
        }

        private void StopIntegratorBtn_Click(object sender, EventArgs e)
        {
            UniSimDesign.SimulationCase sim_case = UnisimWrapper.Instance.FirstSimCase;
            sim_case.Solver.CanSolve = false;
            this.UpdateTimer.Enabled = false;
        }

        private void MainForm_Shown(object sender, EventArgs e)
        {
            Logger.Instance.SetRichBox(this.LoggerView);
            
        }

        private void TestBtn_Click(object sender, EventArgs e)
        {
            UnisimWrapper.Instance.Test();
            
        }

        private void UpdateTimer_Tick(object sender, EventArgs e)
        {
            UniSimDesign.SimulationCase sim_case = UnisimWrapper.Instance.FirstSimCase;
            
            bool solver_enabled = sim_case.Solver.Integrator.IsRunning;
            if (solver_enabled)
                sim_case.Solver.CanSolve = false;

            OPCServerWrapper.Instance.UpdatePropertyListFromOPC(UnisimWrapper.Instance.properties);
            OPCServerWrapper.Instance.WritePropertyListToOPC(UnisimWrapper.Instance.properties);

            if (solver_enabled)
            {
                sim_case.Solver.CanSolve = true;
                sim_case.Solver.Integrator.Continue();
            }
        }
    }
    
    
    public class UnisimWrapper                                                           // Singleton
    {
        private static UnisimWrapper _instance = null;
        public List<UnisimDoublePropertyWrapper> properties;
        
        // Private fields
        private UniSimDesign.Application application_instance = null;

        // Private constructor that takes no arguments
        private UnisimWrapper()
        {
            this.application_instance = new UniSimDesign.Application();
        }
        
        // Instance property
        public static UnisimWrapper Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new UnisimWrapper();
                }
                return _instance;
            }
        }

        private static class RuntimeCastHelper                                           // Самая магия
        {
            public static T GenericCast<T>(object o)
            {
                return (T)o;
            }

            public static object Cast(object o, Type tp){
                MethodInfo cast_method = typeof(RuntimeCastHelper).GetMethod("GenericCast").MakeGenericMethod(tp);
                object ret=null;
                try
                {
                    ret = cast_method.Invoke(null, new object[] { o });
                }
                catch { }
                return ret;
            }
        }

        // Properties
        public UniSimDesign.SimulationCase FirstSimCase
        {
            get
            {
                return (UniSimDesign.SimulationCase)this.application_instance.SimulationCases[0];
            }
        }

        
        // Methods
        public UniSimDesign.SimulationCase NewSimCase(String filename)
        {
            return (UniSimDesign.SimulationCase)this.application_instance.SimulationCases.Add(filename);
        }

        public void MakeProperties(UniSimDesign.SimulationCase simcase)
        {
            this.properties = this.GetMatherialStreamsProperties(simcase);
            this.properties.AddRange(this.GetEnergyStreamsProperties(simcase));
            this.properties.AddRange(this.GetUnitsProperties(simcase));
        }
        
        public List <UnisimDoublePropertyWrapper> GetMatherialStreamsProperties(UniSimDesign.SimulationCase simcase)
        {
            List<UnisimDoublePropertyWrapper> returned_list = new List<UnisimDoublePropertyWrapper>();
            
            
            PropertyInfo[] properties = typeof(UniSimDesign.ProcessStream).GetProperties();

            foreach (UniSimDesign.ProcessStream stream in simcase.Flowsheet.MaterialStreams)
            {
                foreach (PropertyInfo pr in properties)
                {

                    if (pr.PropertyType == typeof(double))
                    {
                        
                        if (pr.CanRead)
                        {
                            double test_read;                                                   // Спорный момент
                            try
                            {
                                test_read = (double)pr.GetValue(stream, null);
                            }
                            catch
                            {
                                continue;
                            }
                        }
                        UnisimDoublePropertyWrapper new_prop = new UnisimDoublePropertyWrapper
                            (
                            stream,                                                          // Unisim ProcessStream COM object
                            pr.SetValue,
                            pr.GetValue,
                            pr.CanRead,
                            pr.CanWrite,
                            "MatherialStreams/" + stream.name + "/" + pr.Name);              // OPC path
                        returned_list.Add(new_prop);
                    }


                }
            }
            return returned_list;
        }

        public List<UnisimDoublePropertyWrapper> GetEnergyStreamsProperties(UniSimDesign.SimulationCase simcase)
        {
            List<UnisimDoublePropertyWrapper> returned_list = new List<UnisimDoublePropertyWrapper>();

            PropertyInfo heat_flow = typeof(UniSimDesign.ProcessStream).GetProperty("HeatFlowValue");
            foreach (UniSimDesign.ProcessStream stream in simcase.Flowsheet.EnergyStreams)
            {
                UnisimDoublePropertyWrapper new_prop = new UnisimDoublePropertyWrapper(
                    stream,                    
                    heat_flow.SetValue,
                    heat_flow.GetValue,
                    true,
                    true,
                    "EnergyStreams/" + stream.name + "/" + heat_flow.Name
                );
                returned_list.Add(new_prop);

            }
            return returned_list;
        }
        
        public List<UnisimDoublePropertyWrapper> GetUnitsProperties(UniSimDesign.SimulationCase simcase)
        {
            List<UnisimDoublePropertyWrapper> returned_list = new List<UnisimDoublePropertyWrapper>();
            
            System.Text.RegularExpressions.Regex IsDigits= new System.Text.RegularExpressions.Regex(@"\d");
            Assembly asm = Assembly.GetAssembly(typeof(UniSimDesign.Valve));
            var q = from t in asm.GetTypes()
                    where t.IsInterface && 
                          t.Namespace == "UniSimDesign" &&
                          !t.Name.Contains("_") &&
                          !t.Name.Contains("Variable") &&
                          !t.Name.Contains("Package") &&
                          !t.Name.Contains("Property") &&
                          (t.Name != "BackDoor") &&
                          !IsDigits.IsMatch(t.Name)
                    select t;
            List<Type> ifaces = q.ToList<Type>();

            foreach (UniSimDesign._IOperation unit in simcase.Flowsheet.Operations)
            {
                String tpname = unit.TypeName.ToLower();
                bool type_defined = false;                                                                 // Индикатор того, что тип определен
                foreach (Type iface in ifaces)
                {
                    String ifname = iface.Name.ToLower();
                    if (ifname.Contains(tpname) || (tpname.Contains(ifname)))
                    {
                        if (RuntimeCastHelper.Cast(unit, iface) != null)
                        {
                            type_defined = true;                                                            // Тип определен
                            PropertyInfo[] properties = iface.GetProperties();
                            foreach (PropertyInfo pr in properties)                                         // Надо бы выделить в отдельный метод
                            {
                                if (pr.PropertyType == typeof(double))
                                {

                                    if (pr.CanRead)
                                    {
                                        double test_read;                                                   // Спорный момент
                                        try
                                        {
                                            test_read = (double)pr.GetValue(unit, null);
                                        }
                                        catch
                                        {
                                            continue;
                                        }
                                    }
                                    UnisimDoublePropertyWrapper new_prop = new UnisimDoublePropertyWrapper
                                        (
                                        unit,                                                          // Unisim ProcessStream COM object
                                        pr.SetValue,
                                        pr.GetValue,
                                        pr.CanRead,
                                        pr.CanWrite,
                                        "Units/" + unit.ClassificationName + "/" + unit.name + "/" + pr.Name);              // OPC path
                                    returned_list.Add(new_prop);
                                }
                            }
                        }
                    }
                    
                }
                if (!type_defined)                                                      // Не удалось по-быстрому определить тип
                {
                    foreach (Type iface in ifaces)
                    {
                        if (RuntimeCastHelper.Cast(unit, iface) != null)
                        {
                            Logger.Instance.Append("Долгое определение типа");
                            Logger.Instance.Append("Тип unit: " + unit.TypeName);
                            Logger.Instance.Append("Действительный тип: " + iface.Name);

                            PropertyInfo[] properties = iface.GetProperties();
                            foreach (PropertyInfo pr in properties)                                         // Надо бы выделить в отдельный метод
                            {
                                if (pr.PropertyType == typeof(double))
                                {

                                    if (pr.CanRead)
                                    {
                                        double test_read;                                                   // Спорный момент
                                        try
                                        {
                                            test_read = (double)pr.GetValue(unit, null);
                                        }
                                        catch
                                        {
                                            continue;
                                        }
                                    }
                                    UnisimDoublePropertyWrapper new_prop = new UnisimDoublePropertyWrapper
                                        (
                                        unit,                                                          // Unisim ProcessStream COM object
                                        pr.SetValue,
                                        pr.GetValue,
                                        pr.CanRead,
                                        pr.CanWrite,
                                        "Units/" + unit.ClassificationName + "/" + unit.name + "/" + pr.Name);              // OPC path
                                    returned_list.Add(new_prop);
                                }
                            }

                        }
                    }
                }
            }

            return returned_list;                                                         
        }

        /*private List<Type> List<T1>(Type type)
        {
            throw new NotImplementedException();
        }*/
        
        public void Test()
        {
            this.MakeProperties(this.FirstSimCase);
            //this.GetUnitsProperties(this.FirstSimCase);
            OPCServerWrapper.Instance.BuildAddressSpace(this.properties);
            OPCServerWrapper.Instance.WritePropertyListToOPC(UnisimWrapper.Instance.properties);
            Logger.Instance.Append("OK");
        }
 
    }

    public class UnisimDoublePropertyWrapper
    {
        // Static fields
        static uint opc_tag_id_counter = 0;

        // Private fields
        private string _opc_path;
        private uint _opc_tag_id;

        private bool _readable;
        private bool _writable;

        private object _target_object;                                                   // UniSim COM object
        public delegate void DoublePropertySetter(object obj,object value, object[] index);
        public delegate object DoublePropertyGetter(object obj, object[] index);
        private DoublePropertySetter _setter;
        private DoublePropertyGetter _getter;
        
        // Constructor
        public UnisimDoublePropertyWrapper(object target_object, DoublePropertySetter setter, DoublePropertyGetter getter, bool readable, bool writable, string opc_path)
        {
            this._target_object = target_object;
            this._setter = setter;
            this._getter = getter;
            this._readable = readable;
            this._writable = writable;

            this._opc_path = opc_path;
            
            this._opc_tag_id = opc_tag_id_counter++;                                     // From static counter
        }

        // Properties
        public double value
        {
            get
            {
                try
                {
                    return (double)this._getter(this._target_object, null);
                }
                catch
                {
                    Logger.Instance.Append("Unisim: Read error for property:" + this.opc_path);
                    return 0;
                }
            }
            set
            {
                try
                {
                    this._setter(this._target_object, value, null);
                }
                catch
                {
                    Logger.Instance.Append("Unisim: Write error for property:" + this.opc_path);
                }
            }
        }

        public string opc_path
        {
            get
            {
                return this._opc_path;
            }
        }

        public uint opc_tag_id
        {
            get
            {
                return this._opc_tag_id;
            }
        }

        public bool Readable
        {
            get
            {
                return this._readable;
            }
        }

        public bool Writable
        {
            get
            {
                return this._writable;
            }
        }

    }

    public class OPCServerWrapper                                                        // Singleton
    {
        private static OPCServerWrapper _instance;
        
        // Private fields
        private DataInterfaceServerLib.DataInterfaceClass data_interface;
        private List<uint> changed_tags_list;                                            // Tag ids of tags changed by OPC client
        private List<double?> previous_values, current_values;                                            

        // Private constructor 
        private OPCServerWrapper() 
        {
            this.data_interface = new DataInterfaceServerLib.DataInterfaceClass();
            this.changed_tags_list = new List<uint>();
            this.previous_values = new List<double?>();
            this.current_values = new List<double?>();
        }

        // Instance property
        public static OPCServerWrapper Instance
        {
            
            get
            {
                if (_instance == null)
                {
                    _instance = new OPCServerWrapper();
                }
                return _instance;
            }
        }
        
        // PrivateMethods
        private void WriteTag(uint tag_id, double value)
        {
            DataInterfaceServerLib.tag_value_t[] values = new DataInterfaceServerLib.tag_value_t[1];
            values[0].tag_id = tag_id;
            values[0].value = value;
            this.data_interface.Write(1, ref values[0]);
        }
        
        private double ReadTag(uint tag_id)
        {
            DataInterfaceServerLib.tag_value_t[] values = new DataInterfaceServerLib.tag_value_t[1];
            values[0].tag_id = tag_id;
            this.data_interface.Read(1, ref values[0]);
            return (double)values[0].value;
        }
        
        // Methods
        public void BuildAddressSpace(List <UnisimDoublePropertyWrapper> prop_list)
        {
            DataInterfaceServerLib.tag_config_t new_tag;
            for (int i = 0; i < prop_list.Count; i++)
            {
                current_values.Add(null);
                previous_values.Add(null);
                
                UnisimDoublePropertyWrapper prop = prop_list[i];
                new_tag = new DataInterfaceServerLib.tag_config_t();
                
                if (prop.Readable && !prop.Writable)                                     // ReadOnly tag
                    new_tag.opc_access = DataInterfaceServerLib.__MIDL___MIDL_itf_DataInterfaceTypeInfo_0000_0000_0001.ReadOnly;
                if (prop.Readable && prop.Writable)                                      // ReadWrite tag
                    new_tag.opc_access = DataInterfaceServerLib.__MIDL___MIDL_itf_DataInterfaceTypeInfo_0000_0000_0001.ReadWrite;
                if (!prop.Readable && prop.Writable)                                     // WriteOnly tag
                    new_tag.opc_access = DataInterfaceServerLib.__MIDL___MIDL_itf_DataInterfaceTypeInfo_0000_0000_0001.WriteOnly;                
                new_tag.opc_path = prop.opc_path;
                new_tag.tag_id = prop.opc_tag_id;
                new_tag.vt = 5; // все будет VT_R8 ибо нефик

                if (i == prop_list.Count - 1)
                {
                    this.data_interface.AddTag(new_tag, 1);
                }
                else
                {
                    this.data_interface.AddTag(new_tag, 0);
                }
            }

        }

        public void WritePropertyListToOPC(List<UnisimDoublePropertyWrapper> property_list)
        {
            for (uint i = 0; i < property_list.Count; i++)
            {
                UnisimDoublePropertyWrapper prop = property_list[(int)i];
                if (prop.Readable)
                {
                    double value = prop.value;
                    this.WriteTag(prop.opc_tag_id, (double)value);
                    previous_values[(int)i] = value;
                }
            }
        }

        public void UpdatePropertyListFromOPC(List<UnisimDoublePropertyWrapper> property_list)
        {
            for (uint i = 0; i < property_list.Count; i++)
            {
                UnisimDoublePropertyWrapper prop = property_list[(int)i];
                if (prop.Writable)
                {
                    double newvalue = this.ReadTag(prop.opc_tag_id);
                    current_values[(int)i] = newvalue;
                }
            }
            
            this.changed_tags_list.Clear();
            for (uint i = 0; i < property_list.Count; i++)
            {
                UnisimDoublePropertyWrapper prop = property_list[(int)i];
                if (prop.Writable)
                    if (current_values[(int)i] != previous_values[(int)i])
                        this.changed_tags_list.Add(i);
            }

            foreach (uint i in this.changed_tags_list)
            {
                Debug.Assert(property_list[(int)i].opc_tag_id == i);
                Debug.Assert(this.current_values[(int)i] != null);
                property_list[(int)i].value = (double)this.current_values[(int)i];
            }
        }

        public bool IsTagsValuesChanged()                                                // Tags values changed by OPC client
        {
            return false;
        }                               
    }

    public class Logger                                                                  // Singleton
    {
        private static Logger instance;

        private RichTextBox view;
        private Logger() { }
        public static Logger Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new Logger();
                }
                return instance;
            }
        }
        
        public void SetRichBox(RichTextBox view)
        {
            this.view = view;
        }

        public void Append(String msg)
        {
            
            Debug.Assert(this.view != null);
            this.view.AppendText(msg + System.Environment.NewLine);
        }
    }


}
